package store

import (
	"errors"
	"fmt"
	"io"
	"net/http"
	"os"
	"sort"
	"strings"
)

type Client struct {
	conf       *config
	httpClient *http.Client
}

func New(ak, sk, endpoint string, configures ...configurer) (*Client, error) {
	conf := &config{securityProvider: &securityProvider{ak: ak, sk: sk}, endpoint: endpoint}
	conf.maxRetryCount = -1
	for _, configure := range configures {
		configure(conf)
	}

	if err := conf.initConfigWithDefault(); err != nil {
		return nil, err
	}
	err := conf.getTransport()
	if err != nil {
		return nil, err
	}

	if isWarnLogEnabled() {
		info := make([]string, 3)
		info[0] = fmt.Sprintf("[OSS SDK Version=%s", sdkVersion)
		info[1] = fmt.Sprintf("Endpoint=%s", conf.endpoint)
		accessMode := "Virtual Hosting"
		if conf.pathStyle {
			accessMode = "Path"
		}
		info[2] = fmt.Sprintf("Access Mode=%s]", accessMode)
		doLog(LEVEL_WARN, strings.Join(info, "];["))
	}
	doLog(LEVEL_DEBUG, "Create client with config:\n%s\n", conf)
	client := &Client{conf: conf, httpClient: &http.Client{Transport: conf.transport, CheckRedirect: checkRedirectFunc}}
	return client, nil
}

func (client Client) Refresh(ak, sk, securityToken string) {
	sp := &securityProvider{ak: strings.TrimSpace(ak), sk: strings.TrimSpace(sk), securityToken: strings.TrimSpace(securityToken)}
	client.conf.securityProvider = sp
}

func (client Client) Close() {
	client.httpClient = nil
	client.conf.transport.CloseIdleConnections()
	client.conf = nil
	SyncLog()
}

func (client Client) ListBuckets(input *ListBucketsInput) (output *ListBucketsOutput, err error) {
	if input == nil {
		input = &ListBucketsInput{}
	}
	output = &ListBucketsOutput{}
	err = client.doActionWithoutBucket("ListBuckets", HttpGet, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) CreateBucket(input *CreateBucketInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("CreateBucketInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("CreateBucket", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) DeleteBucket(bucketName string) (output *BaseModel, err error) {
	output = &BaseModel{}
	err = client.doActionWithBucket("DeleteBucket", HttpDelete, bucketName, defaultSerializable, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetBucketStoragePolicy(input *SetBucketStoragePolicyInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketStoragePolicyInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketStoragePolicy", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}
func (client Client) getBucketStoragePolicyS3(bucketName string) (output *GetBucketStoragePolicyOutput, err error) {
	output = &GetBucketStoragePolicyOutput{}
	var outputS3 *getBucketStoragePolicyOutputS3
	outputS3 = &getBucketStoragePolicyOutputS3{}
	err = client.doActionWithBucket("GetBucketStoragePolicy", HttpGet, bucketName, newSubResourceSerial(SubResourceStoragePolicy), outputS3)
	if err != nil {
		output = nil
		return
	}
	output.BaseModel = outputS3.BaseModel
	output.StorageClass = fmt.Sprintf("%s", outputS3.StorageClass)
	return
}

func (client Client) getBucketStoragePolicy(bucketName string) (output *GetBucketStoragePolicyOutput, err error) {
	output = &GetBucketStoragePolicyOutput{}
	var outputOss *getBucketStoragePolicyOutput
	outputOss = &getBucketStoragePolicyOutput{}
	err = client.doActionWithBucket("GetBucketStoragePolicy", HttpGet, bucketName, newSubResourceSerial(SubResourceStorageClass), outputOss)
	if err != nil {
		output = nil
		return
	}
	output.BaseModel = outputOss.BaseModel
	output.StorageClass = outputOss.StorageClass
	return
}
func (client Client) GetBucketStoragePolicy(bucketName string) (output *GetBucketStoragePolicyOutput, err error) {
	if client.conf.signature == SignatureOss {
		return client.getBucketStoragePolicy(bucketName)
	}
	return client.getBucketStoragePolicyS3(bucketName)
}

func (client Client) ListObjects(input *ListObjectsInput) (output *ListObjectsOutput, err error) {
	if input == nil {
		return nil, errors.New("ListObjectsInput is nil")
	}
	output = &ListObjectsOutput{}
	err = client.doActionWithBucket("ListObjects", HttpGet, input.Bucket, input, output)
	if err != nil {
		output = nil
	} else {
		if location, ok := output.ResponseHeaders[HeaderBucketRegion]; ok {
			output.Location = location[0]
		}
	}
	return
}

func (client Client) ListVersions(input *ListVersionsInput) (output *ListVersionsOutput, err error) {
	if input == nil {
		return nil, errors.New("ListVersionsInput is nil")
	}
	output = &ListVersionsOutput{}
	err = client.doActionWithBucket("ListVersions", HttpGet, input.Bucket, input, output)
	if err != nil {
		output = nil
	} else {
		if location, ok := output.ResponseHeaders[HeaderBucketRegion]; ok {
			output.Location = location[0]
		}
	}
	return
}

func (client Client) ListMultipartUploads(input *ListMultipartUploadsInput) (output *ListMultipartUploadsOutput, err error) {
	if input == nil {
		return nil, errors.New("ListMultipartUploadsInput is nil")
	}
	output = &ListMultipartUploadsOutput{}
	err = client.doActionWithBucket("ListMultipartUploads", HttpGet, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetBucketQuota(input *SetBucketQuotaInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketQuotaInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketQuota", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetBucketQuota(bucketName string) (output *GetBucketQuotaOutput, err error) {
	output = &GetBucketQuotaOutput{}
	err = client.doActionWithBucket("GetBucketQuota", HttpGet, bucketName, newSubResourceSerial(SubResourceQuota), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) HeadBucket(bucketName string) (output *BaseModel, err error) {
	output = &BaseModel{}
	err = client.doActionWithBucket("HeadBucket", HttpHead, bucketName, defaultSerializable, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetBucketMetadata(input *GetBucketMetadataInput) (output *GetBucketMetadataOutput, err error) {
	output = &GetBucketMetadataOutput{}
	err = client.doActionWithBucket("GetBucketMetadata", HttpHead, input.Bucket, input, output)
	if err != nil {
		output = nil
	} else {
		ParseGetBucketMetadataOutput(output)
	}
	return
}

func (client Client) SetObjectMetadata(input *SetObjectMetadataInput) (output *SetObjectMetadataOutput, err error) {
	output = &SetObjectMetadataOutput{}
	err = client.doActionWithBucketAndKey("SetObjectMetadata", HttpPut, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	} else {
		ParseSetObjectMetadataOutput(output)
	}
	return
}

func (client Client) GetBucketStorageInfo(bucketName string) (output *GetBucketStorageInfoOutput, err error) {
	output = &GetBucketStorageInfoOutput{}
	err = client.doActionWithBucket("GetBucketStorageInfo", HttpGet, bucketName, newSubResourceSerial(SubResourceStorageInfo), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) getBucketLocationS3(bucketName string) (output *GetBucketLocationOutput, err error) {
	output = &GetBucketLocationOutput{}
	var outputS3 *getBucketLocationOutputS3
	outputS3 = &getBucketLocationOutputS3{}
	err = client.doActionWithBucket("GetBucketLocation", HttpGet, bucketName, newSubResourceSerial(SubResourceLocation), outputS3)
	if err != nil {
		output = nil
	} else {
		output.BaseModel = outputS3.BaseModel
		output.Location = outputS3.Location
	}
	return
}
func (client Client) getBucketLocation(bucketName string) (output *GetBucketLocationOutput, err error) {
	output = &GetBucketLocationOutput{}
	var outputOss *getBucketLocationOutput
	outputOss = &getBucketLocationOutput{}
	err = client.doActionWithBucket("GetBucketLocation", HttpGet, bucketName, newSubResourceSerial(SubResourceLocation), outputOss)
	if err != nil {
		output = nil
	} else {
		output.BaseModel = outputOss.BaseModel
		output.Location = outputOss.Location
	}
	return
}
func (client Client) GetBucketLocation(bucketName string) (output *GetBucketLocationOutput, err error) {
	if client.conf.signature == SignatureOss {
		return client.getBucketLocation(bucketName)
	}
	return client.getBucketLocationS3(bucketName)
}

func (client Client) SetBucketAcl(input *SetBucketAclInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketAclInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketAcl", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}
func (client Client) getBucketAcl(bucketName string) (output *GetBucketAclOutput, err error) {
	output = &GetBucketAclOutput{}
	var outputOss *getBucketAclOutput
	outputOss = &getBucketAclOutput{}
	err = client.doActionWithBucket("GetBucketAcl", HttpGet, bucketName, newSubResourceSerial(SubResourceAcl), outputOss)
	if err != nil {
		output = nil
	} else {
		output.BaseModel = outputOss.BaseModel
		for i, valGrant := range outputOss.Grants {
			output.Grants[i].Delivered = valGrant.Delivered
			output.Grants[i].Permission = valGrant.Permission
			output.Grants[i].Grantee.DisplayName = valGrant.Grantee.DisplayName
			output.Grants[i].Grantee.ID = valGrant.Grantee.ID
			output.Grants[i].Grantee.Type = valGrant.Grantee.Type
			output.Grants[i].Grantee.URI = GroupAllUsers
		}
	}
	return
}
func (client Client) GetBucketAcl(bucketName string) (output *GetBucketAclOutput, err error) {
	output = &GetBucketAclOutput{}
	if client.conf.signature == SignatureOss {
		return client.getBucketAcl(bucketName)
	}
	err = client.doActionWithBucket("GetBucketAcl", HttpGet, bucketName, newSubResourceSerial(SubResourceAcl), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetBucketPolicy(input *SetBucketPolicyInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketPolicy is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketPolicy", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetBucketPolicy(bucketName string) (output *GetBucketPolicyOutput, err error) {
	output = &GetBucketPolicyOutput{}
	err = client.doActionWithBucketV2("GetBucketPolicy", HttpGet, bucketName, newSubResourceSerial(SubResourcePolicy), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) DeleteBucketPolicy(bucketName string) (output *BaseModel, err error) {
	output = &BaseModel{}
	err = client.doActionWithBucket("DeleteBucketPolicy", HttpDelete, bucketName, newSubResourceSerial(SubResourcePolicy), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetBucketCors(input *SetBucketCorsInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketCorsInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketCors", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetBucketCors(bucketName string) (output *GetBucketCorsOutput, err error) {
	output = &GetBucketCorsOutput{}
	err = client.doActionWithBucket("GetBucketCors", HttpGet, bucketName, newSubResourceSerial(SubResourceCors), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) DeleteBucketCors(bucketName string) (output *BaseModel, err error) {
	output = &BaseModel{}
	err = client.doActionWithBucket("DeleteBucketCors", HttpDelete, bucketName, newSubResourceSerial(SubResourceCors), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetBucketVersioning(input *SetBucketVersioningInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketVersioningInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketVersioning", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetBucketVersioning(bucketName string) (output *GetBucketVersioningOutput, err error) {
	output = &GetBucketVersioningOutput{}
	err = client.doActionWithBucket("GetBucketVersioning", HttpGet, bucketName, newSubResourceSerial(SubResourceVersioning), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetBucketWebsiteConfiguration(input *SetBucketWebsiteConfigurationInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketWebsiteConfigurationInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketWebsiteConfiguration", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetBucketWebsiteConfiguration(bucketName string) (output *GetBucketWebsiteConfigurationOutput, err error) {
	output = &GetBucketWebsiteConfigurationOutput{}
	err = client.doActionWithBucket("GetBucketWebsiteConfiguration", HttpGet, bucketName, newSubResourceSerial(SubResourceWebsite), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) DeleteBucketWebsiteConfiguration(bucketName string) (output *BaseModel, err error) {
	output = &BaseModel{}
	err = client.doActionWithBucket("DeleteBucketWebsiteConfiguration", HttpDelete, bucketName, newSubResourceSerial(SubResourceWebsite), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetBucketLoggingConfiguration(input *SetBucketLoggingConfigurationInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketLoggingConfigurationInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketLoggingConfiguration", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetBucketLoggingConfiguration(bucketName string) (output *GetBucketLoggingConfigurationOutput, err error) {
	output = &GetBucketLoggingConfigurationOutput{}
	err = client.doActionWithBucket("GetBucketLoggingConfiguration", HttpGet, bucketName, newSubResourceSerial(SubResourceLogging), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetBucketLifecycleConfiguration(input *SetBucketLifecycleConfigurationInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketLifecycleConfigurationInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketLifecycleConfiguration", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetBucketLifecycleConfiguration(bucketName string) (output *GetBucketLifecycleConfigurationOutput, err error) {
	output = &GetBucketLifecycleConfigurationOutput{}
	err = client.doActionWithBucket("GetBucketLifecycleConfiguration", HttpGet, bucketName, newSubResourceSerial(SubResourceLifecycle), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) DeleteBucketLifecycleConfiguration(bucketName string) (output *BaseModel, err error) {
	output = &BaseModel{}
	err = client.doActionWithBucket("DeleteBucketLifecycleConfiguration", HttpDelete, bucketName, newSubResourceSerial(SubResourceLifecycle), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetBucketTagging(input *SetBucketTaggingInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketTaggingInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketTagging", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetBucketTagging(bucketName string) (output *GetBucketTaggingOutput, err error) {
	output = &GetBucketTaggingOutput{}
	err = client.doActionWithBucket("GetBucketTagging", HttpGet, bucketName, newSubResourceSerial(SubResourceTagging), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) DeleteBucketTagging(bucketName string) (output *BaseModel, err error) {
	output = &BaseModel{}
	err = client.doActionWithBucket("DeleteBucketTagging", HttpDelete, bucketName, newSubResourceSerial(SubResourceTagging), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetBucketNotification(input *SetBucketNotificationInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetBucketNotificationInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucket("SetBucketNotification", HttpPut, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetBucketNotification(bucketName string) (output *GetBucketNotificationOutput, err error) {
	if client.conf.signature != SignatureOss {
		return client.getBucketNotificationS3(bucketName)
	}
	output = &GetBucketNotificationOutput{}
	err = client.doActionWithBucket("GetBucketNotification", HttpGet, bucketName, newSubResourceSerial(SubResourceNotification), output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) getBucketNotificationS3(bucketName string) (output *GetBucketNotificationOutput, err error) {
	outputS3 := &getBucketNotificationOutputS3{}
	err = client.doActionWithBucket("GetBucketNotification", HttpGet, bucketName, newSubResourceSerial(SubResourceNotification), outputS3)
	if err != nil {
		return nil, err
	}

	output = &GetBucketNotificationOutput{}
	output.BaseModel = outputS3.BaseModel
	topicConfigurations := make([]TopicConfiguration, 0, len(outputS3.TopicConfigurations))
	for _, topicConfigurationS3 := range outputS3.TopicConfigurations {
		topicConfiguration := TopicConfiguration{}
		topicConfiguration.ID = topicConfigurationS3.ID
		topicConfiguration.Topic = topicConfigurationS3.Topic
		topicConfiguration.FilterRules = topicConfigurationS3.FilterRules

		events := make([]EventType, 0, len(topicConfigurationS3.Events))
		for _, event := range topicConfigurationS3.Events {
			events = append(events, ParseStringToEventType(event))
		}
		topicConfiguration.Events = events
		topicConfigurations = append(topicConfigurations, topicConfiguration)
	}
	output.TopicConfigurations = topicConfigurations
	return
}

func (client Client) DeleteObject(input *DeleteObjectInput) (output *DeleteObjectOutput, err error) {
	if input == nil {
		return nil, errors.New("DeleteObjectInput is nil")
	}
	output = &DeleteObjectOutput{}
	err = client.doActionWithBucketAndKey("DeleteObject", HttpDelete, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	} else {
		ParseDeleteObjectOutput(output)
	}
	return
}

func (client Client) DeleteObjects(input *DeleteObjectsInput) (output *DeleteObjectsOutput, err error) {
	if input == nil {
		return nil, errors.New("DeleteObjectsInput is nil")
	}
	output = &DeleteObjectsOutput{}
	err = client.doActionWithBucket("DeleteObjects", HttpPost, input.Bucket, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) SetObjectAcl(input *SetObjectAclInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("SetObjectAclInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucketAndKey("SetObjectAcl", HttpPut, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetObjectAcl(input *GetObjectAclInput) (output *GetObjectAclOutput, err error) {
	if input == nil {
		return nil, errors.New("GetObjectAclInput is nil")
	}
	output = &GetObjectAclOutput{}
	err = client.doActionWithBucketAndKey("GetObjectAcl", HttpGet, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	} else {
		if versionId, ok := output.ResponseHeaders[HeaderVersionId]; ok {
			output.VersionId = versionId[0]
		}
	}
	return
}

func (client Client) RestoreObject(input *RestoreObjectInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("RestoreObjectInput is nil")
	}
	output = &BaseModel{}
	err = client.doActionWithBucketAndKey("RestoreObject", HttpPost, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) GetObjectMetadata(input *GetObjectMetadataInput) (output *GetObjectMetadataOutput, err error) {
	if input == nil {
		return nil, errors.New("GetObjectMetadataInput is nil")
	}
	output = &GetObjectMetadataOutput{}
	err = client.doActionWithBucketAndKey("GetObjectMetadata", HttpHead, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	} else {
		ParseGetObjectMetadataOutput(output)
	}
	return
}

func (client Client) GetObject(input *GetObjectInput) (output *GetObjectOutput, err error) {
	if input == nil {
		return nil, errors.New("GetObjectInput is nil")
	}
	output = &GetObjectOutput{}
	err = client.doActionWithBucketAndKey("GetObject", HttpGet, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	} else {
		ParseGetObjectOutput(output)
	}
	return
}

func (client Client) PutObject(input *PutObjectInput) (output *PutObjectOutput, err error) {
	if input == nil {
		return nil, errors.New("PutObjectInput is nil")
	}

	if input.ContentType == "" && input.Key != "" {
		if contentType, ok := mimeTypes[input.Key[strings.LastIndex(input.Key, ".")+1:]]; ok {
			input.ContentType = contentType
		}
	}

	output = &PutObjectOutput{}
	var repeatable bool
	if input.Body != nil {
		_, repeatable = input.Body.(*strings.Reader)
		if input.ContentLength > 0 {
			input.Body = &readerWrapper{reader: input.Body, totalCount: input.ContentLength}
		}
	}
	if repeatable {
		err = client.doActionWithBucketAndKey("PutObject", HttpPut, input.Bucket, input.Key, input, output)
	} else {
		err = client.doActionWithBucketAndKeyUnRepeatable("PutObject", HttpPut, input.Bucket, input.Key, input, output)
	}
	if err != nil {
		output = nil
	} else {
		ParsePutObjectOutput(output)
	}
	return
}

func (client Client) PutFile(input *PutFileInput) (output *PutObjectOutput, err error) {
	if input == nil {
		return nil, errors.New("PutFileInput is nil")
	}

	var body io.Reader
	sourceFile := strings.TrimSpace(input.SourceFile)
	if sourceFile != "" {
		fd, err := os.Open(sourceFile)
		if err != nil {
			return nil, err
		}
		defer fd.Close()

		stat, err := fd.Stat()
		if err != nil {
			return nil, err
		}
		fileReaderWrapper := &fileReaderWrapper{filePath: sourceFile}
		fileReaderWrapper.reader = fd
		if input.ContentLength > 0 {
			if input.ContentLength > stat.Size() {
				input.ContentLength = stat.Size()
			}
			fileReaderWrapper.totalCount = input.ContentLength
		} else {
			fileReaderWrapper.totalCount = stat.Size()
		}
		body = fileReaderWrapper
	}

	_input := &PutObjectInput{}
	_input.PutObjectBasicInput = input.PutObjectBasicInput
	_input.Body = body
	if _input.ContentType == "" && _input.Key != "" {
		if contentType, ok := mimeTypes[_input.Key[strings.LastIndex(_input.Key, ".")+1:]]; ok {
			_input.ContentType = contentType
		} else if contentType, ok := mimeTypes[sourceFile[strings.LastIndex(sourceFile, ".")+1:]]; ok {
			_input.ContentType = contentType
		}
	}

	output = &PutObjectOutput{}
	err = client.doActionWithBucketAndKey("PutFile", HttpPut, _input.Bucket, _input.Key, _input, output)
	if err != nil {
		output = nil
	} else {
		ParsePutObjectOutput(output)
	}
	return
}

func (client Client) CopyObject(input *CopyObjectInput) (output *CopyObjectOutput, err error) {
	if input == nil {
		return nil, errors.New("CopyObjectInput is nil")
	}

	if strings.TrimSpace(input.CopySourceBucket) == "" {
		return nil, errors.New("Source bucket is empty")
	}
	if strings.TrimSpace(input.CopySourceKey) == "" {
		return nil, errors.New("Source key is empty")
	}

	output = &CopyObjectOutput{}
	err = client.doActionWithBucketAndKey("CopyObject", HttpPut, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	} else {
		ParseCopyObjectOutput(output)
	}
	return
}

func (client Client) AbortMultipartUpload(input *AbortMultipartUploadInput) (output *BaseModel, err error) {
	if input == nil {
		return nil, errors.New("AbortMultipartUploadInput is nil")
	}
	if input.UploadId == "" {
		return nil, errors.New("UploadId is empty")
	}
	output = &BaseModel{}
	err = client.doActionWithBucketAndKey("AbortMultipartUpload", HttpDelete, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) InitiateMultipartUpload(input *InitiateMultipartUploadInput) (output *InitiateMultipartUploadOutput, err error) {
	if input == nil {
		return nil, errors.New("InitiateMultipartUploadInput is nil")
	}

	if input.ContentType == "" && input.Key != "" {
		if contentType, ok := mimeTypes[input.Key[strings.LastIndex(input.Key, ".")+1:]]; ok {
			input.ContentType = contentType
		}
	}

	output = &InitiateMultipartUploadOutput{}
	err = client.doActionWithBucketAndKey("InitiateMultipartUpload", HttpPost, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	} else {
		ParseInitiateMultipartUploadOutput(output)
	}
	return
}

func (client Client) UploadPart(input *UploadPartInput) (output *UploadPartOutput, err error) {
	if input == nil {
		return nil, errors.New("UploadPartInput is nil")
	}

	if input.UploadId == "" {
		return nil, errors.New("UploadId is empty")
	}

	output = &UploadPartOutput{}
	var repeatable bool
	if input.Body != nil {
		_, repeatable = input.Body.(*strings.Reader)
		if _, ok := input.Body.(*readerWrapper); !ok && input.PartSize > 0 {
			input.Body = &readerWrapper{reader: input.Body, totalCount: input.PartSize}
		}
	} else if sourceFile := strings.TrimSpace(input.SourceFile); sourceFile != "" {
		fd, err := os.Open(sourceFile)
		if err != nil {
			return nil, err
		}
		defer fd.Close()

		stat, err := fd.Stat()
		if err != nil {
			return nil, err
		}
		fileSize := stat.Size()
		fileReaderWrapper := &fileReaderWrapper{filePath: sourceFile}
		fileReaderWrapper.reader = fd

		if input.Offset < 0 || input.Offset > fileSize {
			input.Offset = 0
		}

		if input.PartSize <= 0 || input.PartSize > (fileSize-input.Offset) {
			input.PartSize = fileSize - input.Offset
		}
		fileReaderWrapper.totalCount = input.PartSize
		fd.Seek(input.Offset, 0)
		input.Body = fileReaderWrapper
		repeatable = true
	}
	if repeatable {
		err = client.doActionWithBucketAndKey("UploadPart", HttpPut, input.Bucket, input.Key, input, output)
	} else {
		err = client.doActionWithBucketAndKeyUnRepeatable("UploadPart", HttpPut, input.Bucket, input.Key, input, output)
	}
	if err != nil {
		output = nil
	} else {
		ParseUploadPartOutput(output)
		output.PartNumber = input.PartNumber
	}
	return
}

func (client Client) CompleteMultipartUpload(input *CompleteMultipartUploadInput) (output *CompleteMultipartUploadOutput, err error) {
	if input == nil {
		return nil, errors.New("CompleteMultipartUploadInput is nil")
	}

	if input.UploadId == "" {
		return nil, errors.New("UploadId is empty")
	}

	var parts partSlice = input.Parts
	sort.Sort(parts)

	output = &CompleteMultipartUploadOutput{}
	err = client.doActionWithBucketAndKey("CompleteMultipartUpload", HttpPost, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	} else {
		ParseCompleteMultipartUploadOutput(output)
	}
	return
}

func (client Client) ListParts(input *ListPartsInput) (output *ListPartsOutput, err error) {
	if input == nil {
		return nil, errors.New("ListPartsInput is nil")
	}
	if input.UploadId == "" {
		return nil, errors.New("UploadId is empty")
	}
	output = &ListPartsOutput{}
	err = client.doActionWithBucketAndKey("ListParts", HttpGet, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	}
	return
}

func (client Client) CopyPart(input *CopyPartInput) (output *CopyPartOutput, err error) {
	if input == nil {
		return nil, errors.New("CopyPartInput is nil")
	}
	if input.UploadId == "" {
		return nil, errors.New("UploadId is empty")
	}
	if strings.TrimSpace(input.CopySourceBucket) == "" {
		return nil, errors.New("Source bucket is empty")
	}
	if strings.TrimSpace(input.CopySourceKey) == "" {
		return nil, errors.New("Source key is empty")
	}

	output = &CopyPartOutput{}
	err = client.doActionWithBucketAndKey("CopyPart", HttpPut, input.Bucket, input.Key, input, output)
	if err != nil {
		output = nil
	} else {
		ParseCopyPartOutput(output)
		output.PartNumber = input.PartNumber
	}
	return
}
